package org.goldenport.g3

import scala.xml.Node
import java.io.File
import java.net.{URL, URI}
import javax.servlet.http.HttpServletRequest
import com.asamioffice.goldenport.text.StringUtil.makeShortString
import com.asamioffice.goldenport.text.Strings
import org.goldenport.g3.atom._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.sql.SqlDatatype
import org.goldenport.g3.events._
import org.goldenport.g3.events.http.HttpStatusEvent
import org.goldenport.g3.messages.http
import org.goldenport.entity.content.BinaryContent

/**
 * @since   Aug.  3, 2010
 * @version Dec.  5, 2011
 * @author  ASAMI, Tomoharu
 */
class G3AgentContext(
  val signal: Option[AnyRef],
  val session: Option[G3Session],
  val job: Option[G3JobId], // XXX G3Job
  val transaction: Option[G3TransactionId], // XXX G3Transaction
  val unitofwork: Option[G3UnitofworkId], // XXX G3Unitofwork
  val request: Option[HttpServletRequest],
  val context: G3Context
) extends NotNull {
  def getAuthentication(uri: String): Option[G3Authentication] = {
    session.flatMap(_.user.getAuthentication(uri)) orElse
    context.getAuthentication(uri)
  }

  def createDataStoreDriver(uri: String) = context.createDataStoreDriver(uri)

//  def createEntityDriver(uri: String) = context.createEntityDriver(uri)

  def createContentAttachmentsParcel(title: String)(content: AnyRef, attachments: List[AnyRef], source: G3Parcel): G3Parcel = {
    context.createContentAttachmentsParcel(title)(content, attachments, source)
  }

  def sqlDatatypeFactory(uri: String) = context.sqlDatatypeFactory(uri)

  def getJdbcConnection(uri: String, username: String, password: String, driver: String): java.sql.Connection = {
    context.getJdbcConnection(unitofwork, uri, username, password, driver)
  }

  def makeSqlDataType(datatype: XDatatype): SqlDatatype = {
    context.makeSqlDataType(datatype)
  }

  def makeSqlDataType(datatype: XDatatype,
                      multiplicity: Multiplicity): SqlDatatype = {
    context.makeSqlDataType(datatype, multiplicity)
  }

  def reconstitute(uri: String) = context.reconstitute(uri)

  def reconstitute(file: File) = context.reconstitute(file)

  def reconstitute(url: URL) = context.reconstitute(url)

  def getResource(uri: String) = context.getResource(uri)

  def getDataSource(uri: String) = context.getDataSource(uri)

  def getDataSource(uri: URI) = context.getDataSource(uri)

  def httpBaseUri: String = {
    import com.asamioffice.goldenport.servlet.ServletUtil.getContextServletPath

    request match {
      case Some(req) => getContextServletPath(req)
      case None => ""
    }
  }

  def httpGet(get: http.GET) = context.httpGet(get)

  def masterHtml = context.masterHtml

  def masterCss = context.masterCss

  val htmlType = "text/html;charset=utf-8"
//  val atomType = "text/xml;charset=uft-8"
//  val atomType = "text/plain"
//  val atomEntryType = atomType
//  val atomServiceType = atomType
//  val atomCategoryType = atomType
  val atomType = "application/atom+xml;charset=utf-8"
  val atomEntryType = "application/atom+xml;type=entry;charset=utf-8"
  val atomServiceType = "application/atomsvc+xml;charset=utf-8"
  val atomCategoryType = "application/atomcat+xml;charset=utf-8"
  val jsonType = "application/json;charset=utf-8"
  val csvType = "text/csv;charset=utf-8"

  // TODO uri pattern matching selection
  def httpConvertor(cmd: RestCommand): PartialFunction[AnyRef, G3HttpResponse] = {
    val format = {
      if (cmd.uri.endsWith(".atom")) "atom"
      else if (cmd.uri.endsWith(".json")) "json"
      else if (cmd.uri.endsWith(".csv")) "csv"
      else if (cmd.uri.endsWith(".html")) "html"
      else context.getDefaultHttpFormat
    }
    //    println("httpConverter format = " + format)
    format match {
      case "atom" => httpConvertorAtom(cmd)
      case "json" => httpConvertorJson(cmd)
      case "csv" => httpConvertorCsv
      case "html" => httpConvertorHtml
      case "direct" => httpConvertorRaw
      case _ => httpConvertorHtml // XXX httpConvertorRaw
    }
  }

  def httpConvertorAtom(cmd: RestCommand): PartialFunction[AnyRef, G3HttpResponse] = {
    case h: Html => new StringHttpResponse(h.toText(this), htmlType)
    case e: RestEvent => to_feed(e, cmd)
    case e: Exceptioned => e.toAtom
    case e: HttpStatusEvent => e.toAtom
    case f: AtomFeed => to_feed(f)
    case e: AtomEntry => to_feed(e)
    case r: Record => to_feed(r)
    case rs: RecordSet => to_feed(rs)
    case a: AtomFeedable => new StringHttpResponse(a.toAtomFeed(cmd).toText, atomType)
    case a: AppServiceable => new StringHttpResponse(a.toAppService(cmd).toText, atomServiceType)
    case s: String => new StringHttpResponse(Html("", s).toText(this), htmlType)
  }

  private def to_feed(evt: RestEvent, cmd: RestCommand): G3HttpResponse = {
    def feed_entry(entry: AtomEntry): AtomFeed = {
      AtomFeed(
        AtomId(evt.id.toText),
        AtomTitle(evt.uri),
        new AtomUpdated(evt.published),
        entry)
    }

    def feed_record(record: Record): AtomFeed = {
      AtomFeed(
        AtomId(evt.id.toText),
        AtomTitle(evt.uri),
        new AtomUpdated(evt.published),
        record.toAtomEntry)
    }

    def feed_records(records: RecordSet): AtomFeed = {
      val feed = records.toAtomFeed
      feed.atomId = AtomId(evt.id.toText)
      feed.atomTitle = AtomTitle(evt.uri)
      feed.atomUpdated = new AtomUpdated(evt.published)
      feed
    }

    def feed_content(content: AnyRef): AtomFeed = {
      AtomFeed(
        AtomId(evt.id.toText),
        AtomTitle(evt.uri),
        new AtomUpdated(evt.published),
        AtomEntry(
          AtomId(evt.id.toText),
          AtomTitle(evt.uri),
          new AtomPublished(evt.published),
          AtomContent(content)))
    }

    def feed_none: AtomFeed = {
      new AtomFeed(
        AtomId(evt.id.toText),
        AtomTitle(evt.uri),
        new AtomUpdated(evt.published))
    }

    evt.content match {
      case Some(h: Html) => new StringHttpResponse(h.toText(this), htmlType)
      case Some(AtomFeed(feed)) => to_feed(feed)
      case Some(AtomEntry(entry)) => to_feed(feed_entry(entry))
      case Some(record: Record) => to_feed(feed_record(record))
      case Some(records: RecordSet) => to_feed(feed_records(records))
      case Some(a: AtomFeedable) => new StringHttpResponse(a.toAtomFeed(cmd).toText, atomType)
      case Some(a: AppServiceable) => new StringHttpResponse(a.toAppService(cmd).toText, atomServiceType)
      case Some(content) => to_feed(feed_content(content))
      case None => to_feed(feed_none)
    }
  }

  private def to_feed(feed: AtomFeed): G3HttpResponse = {
    new StringHttpResponse(feed.toText, atomType)
  }

  private def to_feed(entry: AtomEntry): G3HttpResponse = {
    val feed = new AtomFeed(entry)
    to_feed(feed)
  }

  private def to_feed(record: Record): G3HttpResponse = {
    val feed = new AtomFeed(record.toAtomEntry)
    to_feed(feed)
  }

  private def to_feed(records: RecordSet): G3HttpResponse = {
    to_feed(records.toAtomFeed)
  }

  def httpConvertorJson(cmd: RestCommand): PartialFunction[AnyRef, G3HttpResponse] = {
    case h: Html => new StringHttpResponse(h.toText(this), htmlType)
    case f: RecordFetched => to_json(f)
    case e: RestEvent => to_json(e, cmd)
    case e: Exceptioned => e.toJson(this, cmd)
    case e: HttpStatusEvent => e.toJson(this, cmd)
    case f: AtomFeed => new StringHttpResponse(f.toJson, jsonType)
    case e: AtomEntry => new StringHttpResponse(e.toJson, jsonType)
    case r: Record => new StringHttpResponse(r.toJson, jsonType)
    case rs: RecordSet => new StringHttpResponse(rs.toJson(cmd), jsonType)
    case j: Jsonable => new StringHttpResponse(j.toJson(cmd), jsonType)
    case s: String => new StringHttpResponse(Html("", s).toText(this), htmlType)
  }

  private def to_json(fetched: RecordFetched) = {
    if (fetched.single) {
      if (fetched.records.isEmpty) {
        throw new NotFoundException("XXX", fetched.uri)
      } else {
        new StringHttpResponse(fetched.records.records.head.toJson, jsonType)
      }
    } else {
      new StringHttpResponse(fetched.records.toJson, jsonType)
    }
  }

  private def to_json(rest: RestEvent, cmd: RestCommand) = {
    rest.content match {
      case Some(b: BinaryContent) => new BinaryContentHttpResponse(b)
      case Some(h: Html) => new StringHttpResponse(h.toText(this), htmlType)
      case Some(AtomFeed(feed)) => new StringHttpResponse(feed.toJson, jsonType)
      case Some(AtomEntry(entry)) => new StringHttpResponse(entry.toJson, jsonType)
      case Some(record: Record) => new StringHttpResponse(record.toJson, jsonType)
      case Some(records: RecordSet) => new StringHttpResponse(records.toJson(cmd), jsonType)
      case Some(j: Jsonable) => new StringHttpResponse(j.toJson(cmd), jsonType)
      case Some(content) => new StringHttpResponse(json_content(content), jsonType)
      case None => new StringHttpResponse("""{}""", jsonType)
    }
  }

  private def json_content(content: Any) = {
    """{"content":"%s"}""".format(content)
  }

  def httpConvertorCsv: PartialFunction[AnyRef, G3HttpResponse] = {
    case h: Html => new StringHttpResponse(h.toText(this), htmlType)
    case e: RestEvent => to_csv(e)
    case f: AtomFeed => new StringHttpResponse(f.toCsv, csvType)
    case e: AtomEntry => new StringHttpResponse(e.toCsv, csvType)
    case r: Record => new StringHttpResponse(r.toCsv, csvType)
    case rs: RecordSet => new StringHttpResponse(rs.toCsv, csvType)
    case s: String => new StringHttpResponse(Html("", s).toText(this), htmlType)
  }

  private def to_csv(rest: RestEvent) = {
    rest.content match {
      case Some(h: Html) => new StringHttpResponse(h.toText(this), htmlType)
      case Some(AtomFeed(feed)) => new StringHttpResponse(feed.toCsv, csvType)
      case Some(AtomEntry(entry)) => new StringHttpResponse(entry.toCsv, csvType)
      case Some(record: Record) => new StringHttpResponse(record.toCsv, csvType)
      case Some(records: RecordSet) => new StringHttpResponse(records.toCsv, csvType)
      case Some(content) => new StringHttpResponse(csv_content(content), csvType)
      case None => new StringHttpResponse("", csvType)
    }
  }

  private def csv_content(content: Any) = {
    text_content(content)
  }

  private def text_content(content: Any) = {
    content match {
      case text: { def toText: String } => text.toText
      case text: { def text: String } => text.text
      case _ => content.toString
    }
  }

  def httpConvertorHtml: PartialFunction[AnyRef, G3HttpResponse] = {
    case h: Html => new StringHttpResponse(h.toText(this), htmlType)
    case e: RestEvent => to_html_rest(e)
    case e: Exceptioned => e.toHtml
    case e: HttpStatusEvent => e.toHtml
    case f: AtomFeed => to_html_content(f)
    case e: AtomEntry => to_html_content(e)
    case r: Record => to_html_content(r)
    case rs: RecordSet => to_html_content(rs)
    case s: String => new StringHttpResponse(Html("", s).toText(this), htmlType)
  }

  private def to_html_rest(rest: RestEvent): G3HttpResponse = {
    def rest_page(node: Node): Node = {
<div>
<div>
<dl>
<dt>REST Event</dt>
<dd>{rest.getClass.getSimpleName}</dd>
<dt>URI</dt>
<dd>{rest.uri}</dd>
<dt>Contents</dt>
<dd>{
  rest.content match {
    case Some(c) => makeShortString(c)
    case None => "-"
  }
}</dd>
</dl>
</div>
<div>{node}</div>
</div>
    }

//    println("to_html: " + rest)
    val r = (rest.content match {
      case Some(c: Html) => c
      case Some(c) => Html(rest.uri, rest_page(get_page(c))) += c
      case None => Html(rest.uri, rest_page(get_page(None)))
    }).toText(this)
//    println("to_html out: " + r)
    new StringHttpResponse(r, htmlType)
  }

  private def to_html_content(content: AnyRef): G3HttpResponse = {
    to_html("", content)
  }

  private def to_html(uri: String, content: AnyRef): G3HttpResponse = {
    val h = Html(uri, get_page(content)) += content
    new StringHttpResponse(h.toText(this), htmlType)
  }
  
  private def get_page(content: AnyRef): Node = {
//    println("get_page: " + content)
    val r = content match {
      case re: RestEvent => re.content match {
        case Some(c) => get_page(c)
        case None => <div></div>
      }
      case rs: RecordSet => <div><g.list/></div>
      case r: Record => <div><g.detail/></div>
      case AtomFeed(f) => <div><g.list/></div>
      case AtomEntry(e) => <div><g.detail/></div>
/*
      case xml: { def toXml(): String } => <div>{xml.toXml}</div>
      case json: { def toJson(): String } => <div>{json.toJson}</div>
      case text: { def toText(): String } => <div>{text.toText}</div>
*/
      case _ => <div></div>
    }
//    println("get_page out: " + r)
    r
  }

  private def to_html(r: Record): String = {
    error("G3AgentContext")
  }

  private def to_html(r: RecordSet): String = {
    error("G3AgentContext")
  }

  def httpConvertorRaw: PartialFunction[AnyRef, G3HttpResponse] = {
    case m: G3TextMimeTyped => new StringHttpResponse(m.toText, m.mimetype)
    case m: G3BinaryMimeTyped => new StringHttpResponse(m.toString, m.mimetype) // FIXME binary data
    case m => new StringHttpResponse("binary:" + m.toString, Strings.mimetype.application_octet_stream) // FIXME binary data
  }

  def urlFetchString(url: URL, encoding: String = "utf-8"): String = {
    context.urlFetchString(url, encoding)
  }

  //
  def localTime2NormalizedTime(time: String): String = {
    time
  }

  // XXX sqlTime2LocalTime?
  // XXX using from sql driver
  def localTime2SqlTime(time: String): String = {
    time
  }
}

object G3AgentContext {
  def apply(parcel: G3Parcel, ctx: G3Context) = {
    new G3AgentContext(
      parcel.properties.get("signal"),
      parcel.session,
      parcel.job,
      parcel.transaction,
      parcel.unitofwork,
      parcel.properties.get("servlet_request").asInstanceOf[Option[HttpServletRequest]],
      ctx)
  }
}
